/**
 * @brief 与 MCU 内核 / 中断 / 看门狗 / Flash 等相关的实现
 * @author 张勇 / 2020-05-13
*/

#include <stdint.h>
#include <cmsis_armcc.h> // for __set_FAULTMASK()
#include <stm32f4xx_ll_iwdg.h>
#include <stm32f4xx_hal_flash.h>

#include "share/cccompat.h"
#include "share/utils.h"
#include "mcu.h"

#include "share/log.h"
#define LOGLEVEL LOG_DEBUG
#define LOGTAG   "MCU"
#include "share/log4app.h"

// STM32 是小端模式
bool mcu_is_big_endian() { return false; }

size_t mcu_align_bytes() {
	// STM32 F0xx/L0xx 系列 MCU 是 4 字节对齐
	// 如果地址未对齐，不可访问 16/32 位变量
	return 4;
}

uint64_t mcu_get_beu64(const void *stream) {
	uint8_t *buf = (uint8_t*)stream;
	return ((uint64_t)buf[0] << 56) | ((uint64_t)buf[1] << 48) | ((uint64_t)buf[2] << 40) | ((uint64_t)buf[3] << 32) |
	       ((uint64_t)buf[4] << 24) | ((uint64_t)buf[5] << 16) | ((uint64_t)buf[6] << 8 ) | ((uint64_t)buf[7]);
}

uint32_t mcu_get_beu32(const void *stream) {
	uint8_t *buf = (uint8_t*)stream;
	return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | (buf[3]);
}

uint16_t mcu_get_beu16(const void *stream) {
	uint8_t *buf = (uint8_t*)stream;
	return (buf[0] << 8) | (buf[1]);
}

uint64_t mcu_get_leu64(const void *stream) {
    // 如果指针本身是 64-bits(8-bytes) 对齐的则直接进行转换
    if((((uintptr_t)stream) & 0x07) == 0) return *((uint64_t*)stream);
	uint8_t *buf = (uint8_t*)stream;
	return ((uint64_t)buf[7] << 56) | ((uint64_t)buf[6] << 48) | ((uint64_t)buf[5] << 40) | ((uint64_t)buf[4] << 32) |
	       ((uint64_t)buf[3] << 24) | ((uint64_t)buf[2] << 16) | ((uint64_t)buf[1] << 8 ) | ((uint64_t)buf[0]);
}

uint32_t mcu_get_leu32(const void *stream) {
    // 如果指针本身是 32-bits(4-bytes) 对齐的则直接进行转换
    if((((uintptr_t)stream) & 0x03) == 0) return *((uint32_t*)stream);
	uint8_t *buf = (uint8_t*)stream;
	return (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | (buf[0]);
}

uint16_t mcu_get_leu16(const void *stream) {
    // 如果指针本身是 16-bits(2-bytes) 对齐的则直接进行转换
    if((((uintptr_t)stream) & 0x01) == 0) return *((uint16_t*)stream);
	uint8_t *buf = (uint8_t*)stream;
	return (buf[1] << 8) | (buf[0]);
}

void* mcu_get_pointer(const void *stream) {
	switch(mcu_pointer_size()) {
	case 8:  return (void*)(uintptr_t)mcu_get_leu64(stream);
	case 4:  return (void*)(uintptr_t)mcu_get_leu32(stream);
	default: return (void*)(uintptr_t)mcu_get_leu16(stream);
	}
}

void mcu_fmc16_write16(uint32_t addr, uint16_t val) {
    if(addr & 0x01) return; // 目标必须 16 位对齐
    *((uint16_t*)addr) = val;
}

void mcu_fmc16_write32(uint32_t addr, uint32_t val) {
    if(addr & 0x03) return; // 目标必须 32 位对齐
    *((uint16_t*)addr) = (uint16_t)(val & 0xFFFF); addr += 2;
    *((uint16_t*)addr) = (uint16_t)((val >> 16) & 0xFFFF);
}

void mcu_fmc32_write32(uint32_t addr, uint32_t val) {
    if(addr & 0x03) return; // 目标必须 32 位对齐
    *((uint32_t*)addr) = val;
}

bool mcu_is_in_ram(uintptr_t addr, uintptr_t len) {
	return ((addr >= MCU_RAM_BEGIN_ADDRESS) && ((addr + len) < MCU_RAM_END_ADDRESS));
}

bool mcu_is_in_flash(uintptr_t addr, uintptr_t len) {
	return (addr >= MCU_FLASH_BEGIN_ADDRESS) && ((addr + len) < MCU_FLASH_END_ADDRESS);
}

bool mcu_is_debugging() {
	return (CoreDebug->DHCSR & 1);
}

// 看门狗初始化
uint32_t mcu_watchdog_timeout_max() {
	// Prescaler=256, Reload=0xFFF, LSI=32KHz
	return 32760;
}

std_err_t mcu_watchdog_init(uint32_t timeout_ms) {
	uint32_t reload = timeout_ms * 32 / 256; // 计算重载值: Prescaler=256, Reload=0xFFF, LSI=32KHz
	if(reload > 0xFFF) return STD_ERR_INVALID_ARG;
	
	LL_IWDG_Enable(IWDG);                     			// 启用 IWDG
	LL_IWDG_SetPrescaler(IWDG, LL_IWDG_PRESCALER_256);  // 预分频 256
	LL_IWDG_SetReloadCounter(IWDG, reload); 			// 设置重载值
	LL_IWDG_ReloadCounter(IWDG);              			// 初始喂狗
	return STD_ERR_OK;
}

void mcu_watchdog_feed(void) {
	LL_IWDG_ReloadCounter(IWDG);  // 写入 0xAAAA
}

void mcu_reboot(uint32_t reason) {
	// 在 RTC 后备区中存放重启原因
	mcu_rtc_bkp_t *bkp = mcu_rtc_bkp_read();
	bkp->boot_reason = reason;
	mcu_rtc_bkp_write(bkp);
	
	__set_PRIMASK(1);
	__set_FAULTMASK(1);
	NVIC_SystemReset();
}

uint32_t mcu_get_reboot_reason(void) {
	// 从 RTC 后备区中获取重启原因
	mcu_rtc_bkp_t *bkp = mcu_rtc_bkp_read();
	return bkp ? bkp->boot_reason : 0;
}

void mcu_clear_reboot_reason(void) {
	// 在 RTC 后备区中存放重启原因
	mcu_rtc_bkp_t *bkp = mcu_rtc_bkp_read();
	bkp->boot_reason = 0;
	mcu_rtc_bkp_write(bkp);
}

uint8_t* mcu_get_chip_id(void) {
	return (uint8_t*)0x1FFFF7E8;
}

// 返回地址所在的扇区
static int _flash_sector(uint32_t addr) {
	addr = addr & 0xFFFF0000;
	switch(addr) {
		case 0x08000000: return 0;
		case 0x08004000: return 1;
		case 0x08008000: return 2;
		case 0x0800C000: return 3;
		case 0x08010000: return 4;
		case 0x08020000: return 5;
		case 0x08040000: return 6;
		case 0x08060000: return 7;
		case 0x08080000: return 8;
		case 0x080A0000: return 9;
		case 0x080C0000: return 10;
		case 0x080E0000: return 11;
		default: return -1;
	}
}

// 返回扇区的起始地址
static uint32_t _flash_sector_addr(uint32_t addr) {
	static const uint32_t _sector_addr[] = {
		0x08000000, 0x08004000, 0x08008000, 0x0800C000, 0x08010000, 
		0x08020000, 0x08040000, 0x08060000, 0x08080000, 0x080A0000, 0x080C0000, 0x080E0000
	};
	
	int sector = _flash_sector(addr);
	return (sector < 0 || sector > 11) ? 0 : _sector_addr[sector];
}

// 返回地址所在的扇区大小
static int _flash_sector_size(uint32_t addr) {
	int sector = _flash_sector(addr);
	if(sector < 0)   return 0;
	if(sector <= 3)  return 16*1024;
	if(sector == 4)  return 64*1024;
	if(sector <= 11) return 128*1024;
	return 0;
}

// 擦除页
static std_err_t _flash_erase(uint32_t addr, uint32_t len) {
	uint32_t sector_size;
	// 检查是否溢出 Flash
	if(!mcu_is_in_flash(addr, len)) { return STD_ERR_OUT_OF_RANGE; }
	// 检查起始地址是否是整页
	if(addr != _flash_sector_addr(addr)) { return STD_ERR_INVALID_SIZE; }	
	// 如果未指定擦除长度，则默认擦除当前页
	if(!len) len = _flash_sector_size(addr);
	
	while(len) {
		// 检查长度是否是整页
		sector_size = _flash_sector_size(addr);
		if(len < sector_size) { return STD_ERR_INVALID_SIZE; }
		
		// 喂一下看门狗，以防长时间操作意外重启
		mcu_watchdog_feed();
		
		// 擦除页
		FLASH_EraseInitTypeDef eraseInit;
		eraseInit.TypeErase = FLASH_TYPEERASE_SECTORS;
		eraseInit.Sector = _flash_sector(addr);
		eraseInit.NbSectors = 1; // 只擦除一个扇区
		eraseInit.VoltageRange = FLASH_VOLTAGE_RANGE_3; // 对于STM32F4,使用3V范围
		if(HAL_FLASHEx_Erase(&eraseInit, NULL) != HAL_OK) { return STD_ERR_FAIL; }
		
		// 下一页
		addr += sector_size; len -= sector_size;
	}
	
	return STD_ERR_OK;
}

// 写入整页数据
static std_err_t flash_write(uint32_t _addr, uint32_t size, const void *_data) {
    // 页起始地址(必须页地址对齐)
    uint32_t* addr = (uint32_t*)_addr;
    // WORD 数据指针
    const uint32_t* data = (uint32_t*)_data;
    // 总 WORD 数量
    uint16_t twc = size/4;
	// Flash 操作状态码/错误码
    HAL_StatusTypeDef fs;

    // WORD 写
    for(int wc=0; wc < twc; wc++) {
        fs = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, (uint32_t)addr, *data);
        if(HAL_OK != fs) { return STD_ERR_FAIL; }
        addr++; data++; // 地址和数据同时向后移4个字节
    }
	
	return STD_ERR_OK;
}

std_err_t mcu_flash_write(uint32_t headAddr, uint32_t len, const void *payload) {
	if(!len || !payload || !mcu_is_in_flash(headAddr, len)) { return STD_ERR_INVALID_ARG; }
	std_err_t err = STD_ERR_OK;
	HAL_StatusTypeDef halerr = HAL_OK;

	// 分配页缓存
	uint8_t *cache = malloc(128*1024); if(!cache) return STD_ERR_NO_MEM;
	// !!! 注意：写 Flash 过程中，禁止中断 !!!
	mcu_pause_irqs(cs);
	
	do {
		// Flash 解锁
		halerr = HAL_FLASH_Unlock(); if(halerr != HAL_OK) { err = STD_ERR_INVALID_STATE; break; }
		
		// 尾部地址
		uint32_t tailAddr = headAddr + len;
		// 缓存数据游标
		uint8_t *pc = cache;
		// 净荷数据游标
		const uint8_t *pp = payload;
		
		// 数据头所在的目标页地址
		uint32_t headPageAddr = _flash_sector_addr(headAddr);
		// 数据头所在的目标页中有多少 dummy 数据
		uint32_t headDummy = headAddr - headPageAddr;
		// 数据头所在的目标页中能存放多少数据
		uint32_t headPayload = _flash_sector_size(headAddr) - headDummy;
		
		// 数据尾所在的目标页地址
		uint32_t tailPageAddr = _flash_sector_addr(tailAddr);
		// 数据尾所在的目标页中能存放多少数据
		uint32_t tailPayload = tailAddr - tailPageAddr;
		// 数据尾所在的目标页中有多少 dummy 数据
		uint32_t tailDummy = _flash_sector_size(tailAddr) - tailPayload;
		
		if(headPageAddr == tailPageAddr) {
			// 头尾在同一页中，也就是说一页就可以写完
			// 读入头部的 dummy 数据到页缓存
			memcpy(pc, (void*)headPageAddr, headDummy); pc += headDummy;
			// 净荷写入页缓存
			memcpy(pc, pp, len); pc += len; pp += len;
			// 读入尾部的 dummy 数据到页缓存
			memcpy(pc, (void*)tailAddr, tailDummy); pc += tailDummy;
			// 擦除
			_flash_erase(headPageAddr, 0);
			// 写入缓存
			flash_write(headPageAddr, _flash_sector_size(headPageAddr), cache); pc = cache;
		} else {
			// 写入头部页
			// 读入头部的 dummy 数据到页缓存
			memcpy(pc, (void*)headPageAddr, headDummy); pc += headDummy;
			// 净荷头部写入页缓存
			memcpy(pc, pp, headPayload); pc += headPayload; pp += headPayload;
			// 擦除
			_flash_erase(headPageAddr, 0);
			// 写入缓存
			flash_write(headPageAddr, _flash_sector_size(headPageAddr), cache); pc = cache;
			
			// 中间页首地址
			uint32_t bodyPageAddr = headPageAddr + _flash_sector_size(headPageAddr);
			// 中间页数据总长度
			uint32_t bodySize = tailPageAddr - bodyPageAddr;
			// 判断是否有中间页
			if(bodySize) {
				// 判断净荷是否也是在 FLASH 存储区中
				if(_flash_sector_addr((uint32_t)pp)) {
					// 净荷在 FLASH 中，必须一次写一页，否则可能有问题
					for(; bodyPageAddr < tailPageAddr; bodyPageAddr += _flash_sector_size(bodyPageAddr)) {
						// 净荷写入页缓存
						memcpy(cache, pp, _flash_sector_size(bodyPageAddr)); pp += _flash_sector_size(bodyPageAddr);
						// 擦除
						_flash_erase(bodyPageAddr, 0);
						// 写入缓存
						flash_write(bodyPageAddr, _flash_sector_size(bodyPageAddr), cache);
					}
				} else {
					// 净荷不是在 SFFS 中，可以一次性写入中间页
					// 整体擦除
					_flash_erase(bodyPageAddr, bodySize);
					// 整体写入
					flash_write(bodyPageAddr, bodySize, pp); pp += bodySize;
				}
			}
			
			// 写入尾部页
			// 净荷尾部写入页缓存
			memcpy(pc, pp, tailPayload); pc += tailPayload; pp += tailPayload;
			// 读入尾部的 dummy 数据到页缓存
			memcpy(pc, (void*)tailAddr, tailDummy); pc += tailDummy;
			// 擦除
			_flash_erase(tailPageAddr, 0);
			// 写入缓存
			flash_write(tailPageAddr, _flash_sector_size(tailPageAddr), cache); pc = cache;
		}
	} while(0);
	
	// ROM 上锁
	HAL_FLASH_Lock();
	// 恢复中断状态
	mcu_resume_irqs(cs);
	// 释放页缓存
	if(cache) free(cache);
	
	return err;
}

std_err_t mcu_init(func_bsp_set_irq_priorities bsp_set_irq_priorities) {
	static std_err_t err;
	
	// 初始化时钟
	err = mcu_clock_init();
	retonerr(err, "_mcu_system_core_clock_set_hse_pll(): %s\r\n", std_err_comment(err));
	
	// 使用 HAL 库之前必须先初始化
	if(HAL_OK != HAL_Init()) return STD_ERR_FAIL;

	// 初始化 NVIC
	mcu_intc_init();
	
	// 调用 BSP 提供的函数: 初始化中断优化先级
	if(bsp_set_irq_priorities) {
		err = bsp_set_irq_priorities();
		retonerr(err, "bsp_enable_bus_clock(): %s\r\n", std_err_comment(err));
	}
	
	return STD_ERR_OK;
}
